<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\annotations\builders;

#use tehframework\annotations\AnnotationDefinition;
#use tehframework\testing\phpunit\TestCase;

require_once 'test/testHelper.php';

class AnnotationBuilderImplTest extends TestCase
{
	const ANNOTATION_NAME  = 'Fake';
	const ANNOTATION_CLASS = 'FakeAnnotation';
	
	function testAutoloadIsDisabledByDefault()
	{
		if (class_exists('FakeAnnotation', false))
		{
			$this->markTestSkipped(
				'FakeAnnotation must not be loaded prior to executing this test.');
		}
		
		$this->setExpectedException('BuilderException');
		
		$builder = new AnnotationBuilderImpl();
		$builder->buildAnnotation(new AnnotationDefinition('Fake'));
	}
	
	function testMapAnnotationToClassThrowsExceptionIfAnnotationNameIsNotNonEmptyString()
	{
		$this->setExpectedException('InvalidArgumentException');
		
		$builder = new AnnotationBuilderImpl();
		$builder->mapAnnotationToClass('', self::ANNOTATION_CLASS);
	}
	
	function testMapAnnotationToClassThrowsExceptionIfClassCouldNotBeFound()
	{
		$this->setExpectedException('InvalidArgumentException');
		
		$builder = new AnnotationBuilderImpl();
		$builder->mapAnnotationToClass(
			self::ANNOTATION_NAME, 'HopyfullyIDoNotExist');
	}
	
	function testMapAnnotationToClassStripsAnnotationSuffixFromName()
	{
		$this->requireClass('tehframework::annotations::MoreFakeAnnotation');
		
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->mapAnnotationToClass('FakeAnnotation', 'MoreFakeAnnotation')
			->buildAnnotation(new AnnotationDefinition('Fake'));
		
		$this->assertType('MoreFakeAnnotation', $annotation);
	}
	
	function testMapAnnotationsToClassesThrowsExceptionIfAnyAnnotationNameIsNotNonEmptyString()
	{
		$this->setExpectedException('InvalidArgumentException');
		
		$builder = new AnnotationBuilderImpl();
		$builder->mapAnnotationsToClasses(array(
			self::ANNOTATION_NAME => self::ANNOTATION_CLASS, self::ANNOTATION_CLASS));
	}
	
	function testMapAnnotationsToClassesThrowsExceptionIfAnyClassCouldNotBeFound()
	{
		$this->setExpectedException('InvalidArgumentException');
		
		$builder = new AnnotationBuilderImpl();
		$builder->mapAnnotationsToClasses(
			array(self::ANNOTATION_NAME => self::ANNOTATION_CLASS,
				'Unknown' => 'HopyfullyIDoNotExist')
		);
	}
	
	function testBuildAnnotationUsesClassMappedToAnnotationName()
	{
		$this->requireClass('tehframework\\annotations\\FakeAnnotation');
		
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->mapAnnotationToClass(self::ANNOTATION_NAME, self::ANNOTATION_CLASS)
			->buildAnnotation(new AnnotationDefinition(self::ANNOTATION_NAME));
		
		$this->assertType(self::ANNOTATION_CLASS, $annotation);
	}
	
	function testBuildAnnotationUsesClassesMappedToAnnotationNames()
	{
		$this->requireClass('tehframework\\annotations\\FakeAnnotation');
		
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->mapAnnotationsToClasses(array(
				self::ANNOTATION_NAME => self::ANNOTATION_CLASS))
			->buildAnnotation(new AnnotationDefinition(self::ANNOTATION_NAME));
		
		$this->assertType(self::ANNOTATION_CLASS, $annotation);
	}
	
	function testBuildAnnotationThrowsExceptionIfAnnotationClassCanNotBeResolved()
	{
		$this->setExpectedException('BuilderException');
		
		$builder = new AnnotationBuilderImpl();
		$builder->buildAnnotation(new AnnotationDefinition('HopefullyIDoNotExist'));
	}
	
	function testBuildAnnotationDoesNotUseAutoloadIfDisabled()
	{
		$this->requireClass('tehframework\\annotations\\FakeAnnotation');
		
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder->disableAutoload()->buildAnnotation(
			new AnnotationDefinition('Fake'));
		
		$this->assertType('FakeAnnotation', $annotation);
	}
	
	function testBuildAnnotationUsesAutoloadIfEnabled()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder->enableAutoload()->buildAnnotation(
			new AnnotationDefinition('Fake'));
		
		$this->assertType('FakeAnnotation', $annotation);
	}
	
	function testBuildAnnotationUsesSpecifiedNamespacesToResolveAnnotationClass()
	{
		if (version_compare(PHP_VERSION, '5.3', '<'))
		{
			$this->markTestSkipped('No namespaces in PHP <5.3.');
		}
		
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->addNamespaces(array('tehframework\\annotations'))
			->enableAutoload()
			->buildTag(new AnnotationDefinition('Fake'));
		
		$this->assertType('tehframework\\annotations\\FakeAnnotation', $annotation);
	}
	
	function testBuildAnnotationThrowsExceptionIfFoundAnnotationClassDoesNotImplementAnnotationInterface()
	{
		$this->setExpectedException('BuilderException');
		
		$builder = new AnnotationBuilderImpl();
		$builder
			->mapAnnotationToClass(self::ANNOTATION_NAME, 'stdClass')
			->buildAnnotation(new AnnotationDefinition(self::ANNOTATION_NAME));
	}
	
	function testBuildAnnotationThrowsExceptionIfSpecifiedTypeDoesNotDeriveFromName()
	{
		$this->setExpectedException('BuilderException');
		
		$builder = new AnnotationBuilderImpl(true);
		$builder->buildAnnotation(new AnnotationDefinition(
			'Fake', 'FooBarAnnotation'));
	}
	
	function testBuildAnnotationConstructsAnnotationWithGivenArguments()
	{
		$foo = 'foo&bar';
		
		$builder = new AnnotationBuilderImpl(true);
		
		$annotation = $builder->buildAnnotation(
			new AnnotationDefinition('Fake', null, null, array($foo)));
		
		$this->assertType('FakeAnnotation', $annotation);
		$this->assertSame($foo, $annotation->getFoo());
	}
	
	function testBuildAnnotationInjectsGivenProperties()
	{
		$bar = 1;
		$baz = 2;
		$wtf = 'amidoing';
		
		$builder = new AnnotationBuilderImpl(true);
		
		$annotation = $builder->buildAnnotation(
			new AnnotationDefinition('Fake', null, null, array(), array(
				'bar' => $bar, 'baz' => $baz, 'wtf' => $wtf)));
		
		$this->assertType('FakeAnnotation', $annotation);
		$this->assertSame($bar, $annotation->bar);
		$this->assertSame($baz, $annotation->baz);
		$this->assertSame($wtf, $annotation->wtf);
	}
	
	function testBuildAnnotationThrowsExceptionIfAnyPropertyDoesNotExistInAnnotationClass()
	{
		$this->setExpectedException('BuilderException');
		
		$builder = new AnnotationBuilderImpl(true);
		
		$annotation = $builder->buildAnnotation(
			new AnnotationDefinition('Fake', null, null, array(), array(
				'hopefullyIDoNotExist' => 'lawl')));
	}
	
	function testBuildAnnotationInjectsBuilderIfAnnotationIsAnnotationBuilderAware()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder->enableAutoload()->buildAnnotation(
			new AnnotationDefinition('FooBar'));
		
		$this->assertTrue($annotation->hasAnnotationBuilder());
	}
	
	function testBuildAnnotationDoesNotBuildNestedAnnotationsByDefault()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder->enableAutoload()->buildAnnotation(
			new AnnotationDefinition('Fake', null, null, array(
				new AnnotationDefinition('FooBar'))));
		
		$this->assertType('AnnotationDefinition', $annotation->getFoo());
		$this->assertSame('FooBar', $annotation->getFoo()->name);
	}
	
	function testBuildAnnotationDoesNotBuildNestedAnnotationsByDefault2()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder->enableAutoload()->buildAnnotation(
			new AnnotationDefinition('Fake', null, null, array(), array(
				'bar' => new AnnotationDefinition('FooBar')))
		);
		
		$this->assertType('AnnotationDefinition', $annotation->bar);
		$this->assertSame('FooBar', $annotation->bar->name);
	}
	
	function testBuildAnnotationBuildsNestedAnnotationsIfEnabled()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->enableAutoload()
			->enableNestedAnnotations()
			->buildAnnotation(
				new AnnotationDefinition('Fake', null, null, array(
					new AnnotationDefinition('FooBar'))));
		
		$this->assertType('FooBarAnnotation', $annotation->getFoo());
	}
	
	function testBuildAnnotationBuildsNestedAnnotationsIfEnabled2()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->enableAutoload()
			->enableNestedAnnotations()
			->buildAnnotation(
				new AnnotationDefinition('Fake', null, null, array(), array(
					'bar' => new AnnotationDefinition('FooBar'))));
		
		$this->assertType('FooBarAnnotation', $annotation->bar);
	}
	
	function testBuildAnnotationBuildsNestedAnnotationsIfEnabled3()
	{
		$builder = new AnnotationBuilderImpl();
		
		$annotation = $builder
			->enableAutoload()
			->enableNestedAnnotations()
			->buildAnnotation(
				new AnnotationDefinition('Fake', null, null,
					array(array(
						new AnnotationDefinition('Fake', 'MoreFake', null, array(1))
					)),
					array('bar' => array(new AnnotationDefinition('FooBar')))));
		
		$foo = $annotation->getFoo();
		$bar = $annotation->bar;
		
		$this->assertType('array', $foo);
		$this->assertType('MoreFakeAnnotation', $foo[0]);
		$this->assertSame(1, $foo[0]->getFoo());
		$this->assertType('array', $bar);
		$this->assertType('FooBarAnnotation', $bar[0]);
	}
}